using UnityEngine;
using System.Collections.Generic;
using System;

namespace Obi
{
    public interface IObiConstraints
    {
        Oni.ConstraintType? GetConstraintType();

        IObiConstraintsBatch GetBatch(int i);
        int batchCount { get; }
        void Clear();

        bool AddToSolver(ObiSolver solver);
        bool RemoveFromSolver();

        int GetConstraintCount();
        int GetActiveConstraintCount();
        void ActivateAllConstraints();
        void DeactivateAllConstraints();

        void Merge(ObiActor actor, IObiConstraints other);
    }

    [Serializable]
    public abstract class ObiConstraints<T> : IObiConstraints where T : class, IObiConstraintsBatch
    {
        [NonSerialized] protected ObiSolver m_Solver;
        [HideInInspector] public List<T> batches = new List<T>();

        public int batchCount { get => batches == null ? 0 : batches.Count; }

        // merges constraints from a given actor with this one.
        public void Merge(ObiActor actor, IObiConstraints other)
        {
            var others = other as ObiConstraints<T>;

            if (others == null || !other.GetConstraintType().HasValue)
                return;

            int constraintType = (int)other.GetConstraintType().Value;

            // clear batch offsets for this constraint type:
            actor.solverBatchOffsets[constraintType].Clear();

            // create new empty batches if needed:
            int newBatches = Mathf.Max(0, others.batchCount - batchCount);
            for (int i = 0;  i < newBatches; ++i)
                AddBatch(CreateBatch());

            for (int i = 0; i < other.batchCount; ++i)
            {
                // store this batch's offset:
                actor.solverBatchOffsets[constraintType].Add(batches[i].activeConstraintCount);

                // merge both batches:
                batches[i].Merge(actor, others.batches[i]);
            }

        }

        public IObiConstraintsBatch GetBatch(int i)
        {
            if (batches != null && i >= 0 && i < batches.Count)
                return (IObiConstraintsBatch) batches[i];
            return null;
        }

        public int GetConstraintCount()
        {
            int count = 0;
            if (batches == null) return count;

            foreach (T batch in batches)
                if (batch != null)
                    count += batch.constraintCount;

            return count;
        }

        public int GetActiveConstraintCount()
        {
            int count = 0;
            if (batches == null) return count;

            foreach (T batch in batches)
                if (batch != null)
                    count += batch.activeConstraintCount;

            return count;
        }

        public void DeactivateAllConstraints()
        {
            if (batches != null)
                foreach (T batch in batches)
                    if (batch != null)
                        batch.DeactivateAllConstraints();
        }

        public void ActivateAllConstraints()
        {
            if (batches != null)
                foreach (T batch in batches)
                    if (batch != null)
                        batch.ActivateAllConstraints();
        }

        public T GetFirstBatch()
        {
            return (batches != null && batches.Count > 0) ? batches[0] : null;
        }

        public Oni.ConstraintType? GetConstraintType()
        {
            if (batches != null && batches.Count > 0)
                return batches[0].constraintType;
            else return null;
        }

        public void Clear()
        {
            RemoveFromSolver();

            if (batches != null)
                batches.Clear();
        }

        public virtual T CreateBatch(T source = null)
        {
            return null;
        }

        public void AddBatch(T batch)
        {
            if (batch != null)
                batches.Add(batch);
        }

        public bool RemoveBatch(T batch)
        {
            if (batches == null || batch == null)
                return false;
            return batches.Remove(batch);
        }

        public bool AddToSolver(ObiSolver solver)
        {

            if (this.m_Solver != null || batches == null)
                return false;

            this.m_Solver = solver;

            foreach (T batch in batches)
                batch.AddToSolver(m_Solver);

            return true;

        }

        public bool RemoveFromSolver()
        {

            if (this.m_Solver == null || batches == null)
                return false;

            foreach (T batch in batches)
                batch.RemoveFromSolver(m_Solver);

            this.m_Solver = null;

            return true;

        }
    }
}
